package main

import (
	"fmt"
	"time"
)

// nil channel 又一个特性, 对其进行读写时都会阻塞
// 与select结合的使用方法
// 第一种用法: 利用default分支避免阻塞
// select 语句的 default 分支的语义，就是在其他非 default 分支因通信未就绪，而无法被选择的时候执行的，
// 这就给 default 分支赋予了一种“避免阻塞”的特性。
func main() {
	// testNormal()

	testNullChannel()

	// 第二种用法： 实现超时机制
	var c chan int
	select {
	case <-c:
	case <-time.After(30 * time.Second):
		// 实现超时机制
		// 作为 time.Timer 的使用者，我们也要尽量减少在使用 Timer 时给 Go 运行时和 Go 垃圾回收带来的压力，要及时调用 timer 的 Stop 方法回收 Timer 资源
		fmt.Println("当前程序超时.")
	}

	// 第三种用法： 实现心跳机制
	// 结合 time 包的 Ticker，可以实现带有心跳机制的 select。这种机制可以在监听 channel 的同时，执行一些周期性的任务，
	heartbeat := time.NewTicker(30 * time.Second)
	defer heartbeat.Stop()
	for {
		select {
		case <-c:
		case <-heartbeat.C:
			fmt.Println("heart beat")
		}
	}
}

/**
 * 会输出
 * 5
 * 许多0
 * 7
 * 因为关闭的channel会输出0
 **/
func testNormal() {
	ch1, ch2 := make(chan int), make(chan int)

	go func() {
		time.Sleep(time.Second * 5)
		ch1 <- 5
		close(ch1)
	}()

	go func() {
		time.Sleep(time.Second * 7)
		ch2 <- 7
		close(ch2)
	}()

	var ok1, ok2 bool
	for {
		select {
		case x := <-ch1:
			ok1 = true
			fmt.Println(x)
		case x := <-ch2:
			ok2 = true
			fmt.Println(x)
		}

		if ok1 && ok2 {
			break
		}
	}

	fmt.Println("program end.")
}

/**
 *	会正确输出
 * 5
 * 7
 **/
func testNullChannel() {
	ch1, ch2 := make(chan int), make(chan int)

	go func() {
		time.Sleep(time.Second * 5)
		ch1 <- 5
		close(ch1)
	}()

	go func() {
		time.Sleep(time.Second * 7)
		ch2 <- 7
		close(ch2)
	}()

	for {
		select {
		case x, ok1 := <-ch1:
			if ok1 {
				fmt.Println(x)
			} else {
				ch1 = nil
			}
		case x, ok2 := <-ch2:
			if ok2 {
				fmt.Println(x)
			} else {
				ch2 = nil
			}
		}

		if ch1 == nil && ch2 == nil {
			break
		}
	}

	fmt.Println("program end.")
}
